#pragma once
#include<iostream>
#include<unistd.h>
#include"Socket.hpp"
#include"Log.hpp"

using namespace SocketModule;
using namespace LogModule;
class SelectServer
{
    const static int size=sizeof(fd_set)*8;//定义一个数组容量的大小
    const static int defaultfd=-1;//文件描述符的缺省值
public:
    SelectServer(int port)//传入端口号构建listen套接字
    : _listensock(std::make_unique<TcpSocket>())//创建一个TCP套接字
    ,_isrunning(false)
    {
        _listensock->BuildTcpSocketMethod(port);//初始化listen套接字
        //通过循环对这个辅助数组进行一个初始化的操作
        for(int i=0;i<size;i++)
        {
            fd_array[i]=defaultfd;
        }
        fd_array[0]=_listensock->Fd();//将_listen套接字的fd放到0号下标中
    }
    //到这里就有了一个listen套接字了
    void Start()//启动监听
    {
        
        _isrunning = true;//运行中，开始监听
        while(_isrunning)
        {
            //因为listensockfd也是一个fd，你进程怎么知道listensockfd上面有新链接到来呢
            // auto res=_listensock->Accept();//我们在select这里，可以进行accept获取链接么？
            //aaccept本质就是阻塞IO。accept关心的事，listensockfd的读事件，将listensockfd的读事件加入到select函数中，让select帮我关心读事件就绪
            
            //结论：新链接到来==读事件就绪

            //将listensockfd的读事件加入到select函数中，让OS帮我关心listensockfd上的读事件

            //读文件描述符集对象，下面我们需要将listensockfd的读事件加入到这个对象中
            fd_set rfds;//定义读文件描述符集合
            FD_ZERO(&rfds);//清空文件描述符集合fds
            
            int maxfd=defaultfd;//获取多个文件描述符中的最大的文件描述符
            //现在需要将这个辅助数组中的文件描述符都添加到efds中
            for(int i=0;i<size;i++)
            {
                if(fd_array[i]==defaultfd)//说明你当前的文件描述符的位置不是一个合法的位置
                    continue;
                //1、每次select之前，都要对rfds进行重置操作
                FD_SET(fd_array[i],&rfds);//将文件描述符添加到rfds中
                //2、更新最大文件描述符值
                if(maxfd<fd_array[i])
                {
                    maxfd=fd_array[i];//更新出最大fd
                }
            }

            //select返回之后，你怎么还知道哪些fd需要被添加到rfds，让select关心呢？
            //所有select要进行完整的设计，要借助一个辅助数组，这个数组保存服务器历史获取过的所有的fd
            //

            //struct timeval timeout={2,0};//每隔两秒让我们的select timeout一次

            //最大fd，一定是变化的。
            //每次select之前，都要对rfds进行重置操作

            //最大文件描述符是_listensock->Fd()+1，让select关心rfds,写事件和读事件都是Nullptr不关心

            //这里第一个参数就是最大的文件描述符+1即可
            int n=select(maxfd+1,&rfds,nullptr,nullptr,nullptr);
            //写、异常、和超时的情况我们都设置为nullptr了

            //对select的返回值进行相关操作
            switch(n)
            {
            case -1 ://返回值是-1的话表示参数传错了
                LOG(LogLevel::ERROR)<<"select error";
                break;
            case 0://返回值是0的话说明是常规的消息，
                LOG(LogLevel::INFO)<<"time out...";
                break;
            default :
            //有事件就绪了
                LOG(LogLevel::DEBUG)<<"有事件就绪了。。。";
                //新链接到来处理就绪的事件
                HandlerEvent();
                break;
            }
        }
        _isrunning = false;//运行结束，监听结束
    }
    //处理就绪的事件
    void HandlerEvent()
    {
        //获取新链接
        InetAddr client;
        int sockfd=_listensock->Accept(&client);//获得对应的链接，这里的accept不会阻塞的

        //这里是不会阻塞的，因为上层的listensock已经就绪了，将等的过程和拷的过程分离了
        if(sockfd>=0)
        {
            //获取新链接成功到来,我们能否直接read/recv()
            //fd是否读继续，我们是不清楚的
            //只有谁最清楚，未来sockfd上是否有事件就绪----select
            //将新的sockefd，托管给select
            //如何托管？将新的fd放入到辅助数组中即可
            LOG(LogLevel::INFO)<<"get a new link ,sockfd:"<<sockfd<<"client is"<<client.StringAddr();
            int pos=0;
            for( ;pos<size;pos++)
            {
                if(fd_array[pos]==defaultfd)//这个位置等于缺省，说明这个位置是没有人占用的
                    break;
            }
            //遍历完了数组都没找到合法的fd
            if(pos==size)
            {
                LOG(LogLevel::WARNING)<<"select server full";//服务器被打满了
                close(sockfd);
            }
            else //找到了合法的位置
            {
                fd_array[pos]=s
            }
        }
    }
    void Stop()//停止监听
    {
        _isrunning = false;//停止监听
    }
    ~SelectServer()
    {

    }
private:
    std::unique_ptr<Socket>_listensock;//创建一个基类套接字
    bool _isrunning = false;

    int fd_array[size];
};